﻿using Common;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

// ReSharper disable InconsistentNaming

// ReSharper disable StringLiteralTypo
#pragma warning disable IDE0044
#pragma warning disable CS0414 // Field is assigned but its value is never used

namespace MyCanFD
{
    /// <summary>
    /// 管理 CAN FD 设备的类，负责设备的打开、初始化、配置、发送和接收消息等操作。
    /// </summary>
    public class CanFdManager
    {
        // Constants for CAN FD flags
        private const int CanFdBitRateSwitchFlag = 0x01; // Bit rate switch for payload data

        private const int CanFdErrorStateIndicatorFlag = 0x02; // Error state indicator

        // CAN payload length and DLC definitions
        private const int CanMaxDlc = 8;

        private const int CanMaxDataLength = 8;

        // CAN FD payload length and DLC definitions
        private const int CanFdMaxDlc = 15;

        private const int CanFdMaxDataLength = 64;

        // CAN frame flags
        private const uint CanExtendedFrameFlag = 0x80000000U; // Extended frame flag

        private const uint CanRemoteTransmissionRequestFlag = 0x40000000U; // Remote transmission request
        private const uint CanErrorFrameFlag = 0x20000000U; // Error frame flag
        private const uint CanIdentifierMask = 0x1FFFFFFFU; // Identifier mask

        // Device and channel configuration
        private readonly DeviceInfo _deviceInfo = new DeviceInfo(Define.ZCAN_USBCANFD_200U, count: 2);

        private int _canFdStandard = 0; // 0: CAN FD ISO, 1: CAN FD BOSCH
        private int _workMode = 0; // 0: Normal mode, 1: Listen-only mode
        private int _filterMode = 2; // 0: Standard frame, 1: Extended frame, 2: Disabled
        private int _frameType = 0; // 0: Standard frame, 1: Extended frame
        private int _protocolType = 1; // 0: CAN, 1: CAN FD
        private int _bitRateSwitchEnabled = 0; // 0: Disabled, 1: Enabled
        private int _sendType = 0; // 0: Normal send
        private int _channelIndex = 0;

        /// <summary>
        /// 设备名
        /// </summary>
        public String DeviceName { get; set; } = "CANFD";

        // Device handles
        private IntPtr _deviceHandle;

        private IntPtr _channelHandle1;
        private IntPtr _channelHandle2;

        // Property interface for device configuration
        private IProperty _deviceProperty;

        /// <summary>
        /// Data queue for received CAN FD messages
        /// </summary>
        public Queue<CanFdData> ReceivedDataQueue { get; set; } = new Queue<CanFdData>();

        /// <summary>
        /// 波特率仲裁域。
        /// </summary>
        public uint Baud_Domain { get; set; } = 500000;

        /// <summary>
        /// 波特率数据域。
        /// </summary>
        public uint Baud_Data { get; set; } = 2000000;

        // Lock object for thread-safe access to received data
        private readonly object _receivedDataLock = new object();

        private readonly object _sendLock = new object();

        // Device state flags
        private bool _isDeviceOpen = false;

        private bool _isDeviceInitialized = false;
        private bool _isMonitoringActive = false;

        /// <summary>
        /// 接收队列的最大容量。
        /// </summary>
        public int QueueSize { get; set; } = 1024;

        /// <summary>
        /// 打开 CAN FD 设备。
        /// </summary>
        /// <exception cref="InvalidOperationException">如果设备已经打开，则抛出此异常。</exception>
        /// <exception cref="Exception">如果设备打开失败，则抛出此异常。</exception>
        public void OpenDevice()
        {
            Mylog.Info($"{DeviceName} {nameof(OpenDevice)} start");
            if (_isDeviceOpen)
            {
                Mylog.Info($"{DeviceName} {nameof(OpenDevice)} error: Device is already open.");
                throw new InvalidOperationException("Device is already open.");
            }

            _deviceHandle = Method.ZCAN_OpenDevice(_deviceInfo.device_type, 0, 0);
            if (_deviceHandle == IntPtr.Zero)
            {
                Mylog.Info($"{DeviceName} {nameof(OpenDevice)} error: handle is zero");
                throw new Exception("Failed to open device. Please check the device type and index.");
            }

            _isDeviceOpen = true;
            Mylog.Info($"{DeviceName} {nameof(OpenDevice)} finished");
            Thread.Sleep(500); // Allow time for device initialization
        }

        /// <summary>
        /// 设置 CAN FD 设备的波特率。
        /// </summary>
        /// <param name="domain">'A' 表示仲裁域，'D' 表示数据域。</param>
        /// <param name="baudRate">要设置的波特率。</param>
        /// <returns>如果波特率设置成功，返回 true；否则返回 false。</returns>
        private bool SetBaudRate(char domain, uint baudRate)
        {
            Mylog.Info($"{DeviceName} {nameof(SetBaudRate)} start");
            string value = baudRate.ToString();
            string pathPrefix = domain == 'A' ? "canfd_abit_baud_rate" : "canfd_dbit_baud_rate";

            if (_deviceProperty.SetValue($"0/{pathPrefix}", value) != 1)
            {
                Mylog.Info($"{DeviceName} {nameof(SetBaudRate)} ch0 error");
                return false;
            }

            if (_deviceProperty.SetValue($"1/{pathPrefix}", value) != 1)
            {
                Mylog.Info($"{DeviceName} {nameof(SetBaudRate)} ch1 error");
                return false;
            }

            Mylog.Info($"{DeviceName} {nameof(SetBaudRate)} finished");
            return true;
        }

        /// <summary>
        /// 设置 CAN FD 标准。
        /// </summary>
        /// <param name="standard">0 表示 CAN FD ISO，1 表示 CAN FD BOSCH。</param>
        /// <returns>如果标准设置成功，返回 true；否则返回 false。</returns>
        private bool SetCanFdStandard(int standard)
        {
            Mylog.Info($"{DeviceName} {nameof(SetCanFdStandard)} start");
            string path = $"{_channelIndex}/canfd_standard";
            var result = _deviceProperty.SetValue(path, standard.ToString()) == 1;

            Mylog.Info($"{DeviceName} {nameof(SetCanFdStandard)} finished {((result == false) ? "error" : "")}");

            return result;
        }

        /// <summary>
        /// 初始化 CAN FD 设备。
        /// </summary>
        /// <exception cref="InvalidOperationException">如果设备未打开，则抛出此异常。</exception>
        /// <exception cref="Exception">如果初始化失败，则抛出此异常。</exception>
        private void InitializeDevice()
        {
            Mylog.Info($"{DeviceName} {nameof(InitializeDevice)} start");
            if (!_isDeviceOpen)
            {
                Mylog.Info($"{DeviceName} {nameof(InitializeDevice)}error:Device is not open.");
                throw new InvalidOperationException("Device is not open.");
            }

            if (_isDeviceInitialized)
            {
                return;
            }

            IntPtr propertyPtr = Method.GetIProperty(_deviceHandle);
            if (propertyPtr == IntPtr.Zero)
            {
                Mylog.Info($"{DeviceName} {nameof(InitializeDevice)}error: Failed to get device properties handle.");
                throw new Exception("Failed to get device properties.");
            }

            _deviceProperty = (IProperty)Marshal.PtrToStructure(propertyPtr, typeof(IProperty));

            if (!SetCanFdStandard(_canFdStandard))
            {
                throw new Exception("Failed to set CAN FD standard.");
            }

            if (!SetBaudRate('A', Baud_Domain))
            {
                throw new Exception("Failed to set arbitration baud rate.");
            }

            if (!SetBaudRate('D', Baud_Data))
            {
                throw new Exception("Failed to set data baud rate.");
            }

            ZCAN_CHANNEL_INIT_CONFIG config = new ZCAN_CHANNEL_INIT_CONFIG
            {
                canfd = { mode = (byte)_workMode },
                can_type = Define.TYPE_CANFD
            };

            IntPtr configPtr = Marshal.AllocHGlobal(Marshal.SizeOf(config));
            Marshal.StructureToPtr(config, configPtr, true);

            if (configPtr == IntPtr.Zero)
            {
                Mylog.Info($"{DeviceName} {nameof(InitializeDevice)} error: Failed to allocate memory for channel configuration.");
                throw new Exception("Failed to allocate memory for channel configuration.");
            }
            _channelHandle1 = Method.ZCAN_InitCAN(_deviceHandle, 0, configPtr);
            _channelHandle2 = Method.ZCAN_InitCAN(_deviceHandle, 1, configPtr);
            Marshal.FreeHGlobal(configPtr);

            if (_channelHandle1 == IntPtr.Zero || _channelHandle2 == IntPtr.Zero)
            {
                Mylog.Info($"{DeviceName} {nameof(InitializeDevice)} error:Failed to initialize CAN channels.");
                throw new Exception("Failed to initialize CAN channels.");
            }

            _isDeviceInitialized = true;
            Mylog.Info($"{DeviceName} {nameof(InitializeDevice)} finished");
        }

        /// <summary>
        /// 启动 CAN FD 消息监控。
        /// </summary>
        /// <exception cref="InvalidOperationException">如果设备未打开或未初始化，则抛出此异常。</exception>
        private void StartMonitoring()
        {
            Mylog.Info($"{DeviceName} {nameof(StartMonitoring)} start");
            if (!_isDeviceOpen || !_isDeviceInitialized)
            {
                throw new InvalidOperationException("Device is not open or initialized.");
            }

            if (Method.ZCAN_StartCAN(_channelHandle1) != Define.STATUS_OK ||
                Method.ZCAN_StartCAN(_channelHandle2) != Define.STATUS_OK)
            {
                throw new Exception("Failed to start CAN channels.");
            }

            Method.ZCAN_ClearBuffer(_channelHandle1);
            _isMonitoringActive = true;
            _monitorThread = new Thread(MonitorCanFdMessages);
            _monitorThread.IsBackground = true;
            _monitorThread.Start();

            Mylog.Info($"{DeviceName} {nameof(StartMonitoring)} finished");
        }

        private Thread _monitorThread;

        /// <summary>
        /// 监控 CAN FD 消息。
        /// </summary>
        private void MonitorCanFdMessages()
        {
            Mylog.Info($"{DeviceName} {nameof(MonitorCanFdMessages)} start");
            while (_isMonitoringActive)
            {
                ReceiveCanFdMessages(_channelHandle1, 0);
                ReceiveCanFdMessages(_channelHandle2, 1);
                Thread.Sleep(10);
            }
            Mylog.Info($"{DeviceName} {nameof(StartMonitoring)} finished");
        }

        /// <summary>
        /// 从指定通道接收 CAN FD 消息。
        /// </summary>
        /// <param name="channelHandle">要接收消息的通道句柄。</param>
        /// <param name="channelIndex">通道索引。</param>
        private void ReceiveCanFdMessages(IntPtr channelHandle, int channelIndex)
        {
            ZCAN_ReceiveFD_Data[] canFdData = new ZCAN_ReceiveFD_Data[64];
            uint messageCount = Method.ZCAN_GetReceiveNum(channelHandle, Define.TYPE_CANFD);

            if (messageCount > 0)
            {
                try
                {
                    int dataSize = Marshal.SizeOf(typeof(ZCAN_ReceiveFD_Data));
                    IntPtr dataPtr = Marshal.AllocHGlobal((int)messageCount * dataSize);
                    messageCount = Method.ZCAN_ReceiveFD(channelHandle, dataPtr, messageCount, 50);

                    for (int i = 0; i < messageCount; i++)
                    {
                        canFdData[i] = (ZCAN_ReceiveFD_Data)Marshal.PtrToStructure(
                            dataPtr + i * dataSize, typeof(ZCAN_ReceiveFD_Data));
                    }

                    ParseCanFdMessages(canFdData, messageCount, channelIndex);
                    Marshal.FreeHGlobal(dataPtr);
                }
                catch (Exception ex)
                {
                    Mylog.Error(ex: ex, message: $"Error receiving CAN FD messages: {ex.Message}", deviceName: DeviceName);
                }
            }
        }

        /// <summary>
        /// 解析接收到的 CAN FD 消息。
        /// </summary>
        /// <param name="messages">接收到的消息数组。</param>
        /// <param name="messageCount">消息数量。</param>
        /// <param name="channelIndex">通道索引。</param>
        private void ParseCanFdMessages(ZCAN_ReceiveFD_Data[] messages, uint messageCount, int channelIndex)
        {
            for (int i = 0; i < messageCount; i++)
            {
                ZCAN_ReceiveFD_Data message = messages[i];
                uint id = message.frame.can_id;
                uint formattedId = id & CanIdentifierMask;
                byte[] data = message.frame.data;
                UInt64 timestamp = message.timestamp;

                lock (_receivedDataLock)
                {
                    if (ReceivedDataQueue.Count > QueueSize)
                    {
                        ReceivedDataQueue.Dequeue();
                    }
                    ReceivedDataQueue.Enqueue(new CanFdData { CH = channelIndex, Data = data, ID = formattedId, TimeStamp = timestamp });
                }

                Mylog.DeviceData(receivedData: $"channel=[{channelIndex}]id=[{formattedId:X8}]data=[{BitConverter.ToString(data).Replace("-", " ")}]", deviceName: DeviceName);
            }
        }

        /// <summary>
        /// 根据 CAN ID 查找接收到的数据。
        /// </summary>
        /// <param name="canfdId">要查找的 CAN ID。</param>
        /// <param name="ch">通道索引（默认为 0）。</param>
        /// <returns>匹配的 CAN FD 数据列表。</returns>
        public List<CanFdData> FindDataByID(uint canfdId, int ch = 0)
        {
            List<CanFdData> canFdDatas;

            lock (_receivedDataLock)
            {
                canFdDatas = ReceivedDataQueue.Where(x => x.ID == canfdId && x.CH == ch).ToList();
            }
            return canFdDatas;
        }

        /// <summary>
        /// 关闭 CAN FD 设备。
        /// </summary>
        public void CloseDevice()
        {
            Mylog.Info($"{DeviceName} {nameof(CloseDevice)} start");
            if (!_isDeviceOpen)
            {
                Mylog.Info($"{DeviceName} {nameof(CloseDevice)} not open");
                return;
            }

            _isDeviceOpen = false;
            _isDeviceInitialized = false;
            _isMonitoringActive = false;
            _monitorThread?.Join();
            if (_channelHandle1 != IntPtr.Zero)
            {
                Method.ZCAN_ResetCAN(_channelHandle1);
                _channelHandle1 = IntPtr.Zero;
            }
            if (_channelHandle2 != IntPtr.Zero)
            {
                Method.ZCAN_ResetCAN(_channelHandle2);
                _channelHandle2 = IntPtr.Zero;
            }
            //Method.ZCAN_ResetCAN(_channelHandle1);
            //Method.ZCAN_ResetCAN(_channelHandle2);
            var tmp = Method.ZCAN_CloseDevice(_deviceHandle);

            Mylog.Info($"{DeviceName} {nameof(CloseDevice)} finished");
        }

        /// <summary>
        /// 发送 CAN FD 消息。
        /// </summary>
        /// <param name="id">CAN ID（十六进制格式）。</param>
        /// <param name="data">要发送的数据。</param>
        /// <param name="channel">发送消息的通道（0 或 1）。</param>
        /// <param name="frametype">帧类型0 标准帧 1扩展帧</param>
        public void SendMessage(uint id, string data, int channel, int frametype = 0)
        {
            if (!_isDeviceOpen)
            {
                return;
            }

            lock (_sendLock)
            {
                uint canId = id;

                ZCAN_TransmitFD_Data canFdData = new ZCAN_TransmitFD_Data();
                canFdData.frame.can_id = MakeCanId(canId, frametype, 0, 0);
                canFdData.frame.data = new byte[64];
                canFdData.frame.len = (byte)SplitData(data, ref canFdData.frame.data, CanFdMaxDataLength);
                canFdData.transmit_type = (uint)_sendType;
                canFdData.frame.flags = (byte)((_bitRateSwitchEnabled != 0) ? CanFdBitRateSwitchFlag : 0);
                IntPtr ptr = Marshal.AllocHGlobal(Marshal.SizeOf(canFdData));
                Marshal.StructureToPtr(canFdData, ptr, true);
                uint result = 0;

                result = Method.ZCAN_TransmitFD((channel == 0) ? _channelHandle1 : _channelHandle2, ptr, 1);
                Marshal.FreeHGlobal(ptr);

                Mylog.DeviceData(sendData: $"channel=[{channel}]id=[{id:X8}]data=[{data}]", deviceName: DeviceName);

                if (result != 1)
                {
                    LogError();
                }
            }
        }

        /// <summary>
        /// 清理缓冲区。
        /// </summary>
        /// <param name="channel">通道索引。</param>
        public void ClearBuffer(int channel)
        {
            Mylog.Info($"{DeviceName} {nameof(ClearBuffer)} start");
            Method.ZCAN_ClearBuffer(channel == 0 ? _channelHandle1 : _channelHandle2);
            lock (_receivedDataLock)
            {
                ReceivedDataQueue.Clear();
            }
            Mylog.Info($"{DeviceName} {nameof(ClearBuffer)} finished");
        }

        /// <summary>
        /// 发送消息并等待响应。
        /// </summary>
        /// <param name="sendId">发送的 CAN ID。</param>
        /// <param name="message">要发送的消息。</param>
        /// <param name="respId">预期的响应 CAN ID。</param>
        /// <param name="chl">通道索引（默认为 0）。</param>
        /// <returns>收到的第一条 CAN FD 数据。</returns>
        /// <exception cref="Exception">如果超时未收到响应，则抛出此异常。</exception>
        public CanFdData sendMessageWaitResponse(uint sendId, byte[] message, uint respId, int chl = 0)
        {
            ClearBuffer(chl);
            SendMessage(sendId, message, 0);
            Stopwatch sw = Stopwatch.StartNew();
            List<CanFdData> datas;
            while ((datas = FindDataByID(respId)).Count == 0)
            {
                if (sw.ElapsedMilliseconds > 3000)
                {
                    throw new Exception("canfd wait response timeout");
                }
                Thread.Sleep(10);
            }
            return datas[0];
        }

        /// <summary>
        /// 发送 CAN FD 消息。
        /// </summary>
        /// <param name="id">CAN ID。</param>
        /// <param name="data">要发送的数据。</param>
        /// <param name="channel">发送消息的通道（0 或 1）。</param>
        /// <param name="frametype">帧类型0 标准帧 1扩展帧</param>
        /// <exception cref="Exception">如果数据长度超出范围，则抛出此异常。</exception>
        public void SendMessage(uint id, byte[] data, int channel, int frametype = 0)
        {
            if (!_isDeviceOpen)
            {
                return;
            }
            if (data.Length > CanFdMaxDataLength)
            {
                throw new Exception("canfd send data length out range");
            }

            lock (_sendLock)
            {
                uint canId = id;

                ZCAN_TransmitFD_Data canFdData = new ZCAN_TransmitFD_Data();
                canFdData.frame.can_id = MakeCanId(canId, frametype, 0, 0);
                canFdData.frame.data = new byte[64];
                data.CopyTo(canFdData.frame.data, 0);
                canFdData.frame.len = (byte)data.Length;
                canFdData.transmit_type = (uint)_sendType;
                canFdData.frame.flags = (byte)((_bitRateSwitchEnabled != 0) ? CanFdBitRateSwitchFlag : 0);
                IntPtr ptr = Marshal.AllocHGlobal(Marshal.SizeOf(canFdData));
                Marshal.StructureToPtr(canFdData, ptr, true);
                uint result = Method.ZCAN_TransmitFD((channel == 0) ? _channelHandle1 : _channelHandle2, ptr, 1);
                Marshal.FreeHGlobal(ptr);
                Mylog.DeviceData(sendData: $"channel=[{channel}]id=[{id:X8}]data=[{BitConverter.ToString(data).Replace("-", " ")}]", deviceName: DeviceName);

                if (result != 1)
                {
                    LogError();
                }
            }
        }

        private uint MakeCanId(uint id, int eff, int rtr, int err)//1:extend frame 0:standard frame
        {
            uint ueff = (uint)(!!(Convert.ToBoolean(eff)) ? 1 : 0);
            uint urtr = (uint)(!!(Convert.ToBoolean(rtr)) ? 1 : 0);
            uint uerr = (uint)(!!(Convert.ToBoolean(err)) ? 1 : 0);
            return id | ueff << 31 | urtr << 30 | uerr << 29;
        }

        /// <summary>
        /// 将字符串数据分割为字节数组。
        /// </summary>
        /// <param name="data">要分割的字符串数据。</param>
        /// <param name="transData">目标字节数组。</param>
        /// <param name="maxLen">最大长度。</param>
        /// <returns>实际分割的字节数。</returns>
        private int SplitData(string data, ref byte[] transData, int maxLen)
        {
            int retLen = 0;
            string[] dataArray = data.Split(' ');
            for (int i = 0; (i < maxLen) && (i < dataArray.Length); i++)
            {
                transData[i] = Convert.ToByte(dataArray[i].Substring(0, 2), 16);
                retLen++;
            }

            return retLen;
        }

        /// <summary>
        /// 记录发送消息失败的错误信息。
        /// </summary>
        private void LogError()
        {
            ZCAN_CHANNEL_ERROR_INFO errorInfo = new ZCAN_CHANNEL_ERROR_INFO();
            IntPtr errorPtr = Marshal.AllocHGlobal(Marshal.SizeOf(errorInfo));
            Marshal.StructureToPtr(errorInfo, errorPtr, true);

            if (Method.ZCAN_ReadChannelErrInfo(_channelHandle1, errorPtr) != Define.STATUS_OK)
            {
                Mylog.Error(message: "Failed to read error information.", deviceName: DeviceName);
            }

            Marshal.FreeHGlobal(errorPtr);
            Mylog.Error(message: $"Error sending message: {errorInfo.error_code}", deviceName: DeviceName);
        }

        /// <summary>
        /// 初始化并启动 CAN FD 设备。
        /// </summary>
        public void InitializeAndStart()
        {
            Mylog.Info($"{DeviceName} {nameof(InitializeAndStart)} start");
            InitializeDevice();
            Thread.Sleep(500);
            StartMonitoring();
            Mylog.Info($"{DeviceName} {nameof(InitializeAndStart)} finished");
        }
    }
}